package ro.uaic.info.fmse.context.transformers.generic;
import java.util.ArrayList;
import java.util.List;

import ro.uaic.info.fmse.context.transformers.enums.KType;
import ro.uaic.info.fmse.context.transformers.items.ConfigurationNode;
import ro.uaic.info.fmse.context.transformers.items.MyCloneable;
public class Node<T extends MyCloneable> {
	
	public int ID;
	public String label;
	public KType type;
	public Node<T> parent;
	public List<Node<T>> children;
	private String data;
	
	public T property;
	
	/**
	 * Node - corresponds to a K cell.
	 * 
	 * @param iD
	 * @param label
	 * @param type
	 * @param parent
	 * @param children
	 * @param property
	 */
	public Node(int iD, String label, KType type, T property, String data) {
		ID = iD;
		this.label = label;
		this.type = type;
		this.parent = null;
		this.children = null;
		this.property = property;
		this.data = data;
	 }
	
	 public Node(String label, KType type, T property) 
	 {
		 ID = this.hashCode(); 
		 this.label = label; 
		 this.type = type; 
		 this.parent = null; 
		 this.children = null;
		 this.property = property; 
	 }
	
	
	private Node(int iD, String label, KType type, Node<T> parent,
			List<Node<T>> children, T property, String data) {
		super();
		ID = iD;
		this.label = label;
		this.type = type;
		this.parent = parent;
		this.children = children;
		this.property = property;
		this.data = data;
	}
	
	
	public void appendChild(Node<T> child) {
		if (children == null)
			children = new ArrayList<Node<T>>();
		child.parent = this;
		children.add(child);
	}
	
	@SuppressWarnings("unchecked")
	@Override
	protected Object clone() throws CloneNotSupportedException {
		ArrayList<Node<T>> list = new ArrayList<Node<T>>();
		if (children == null)
			list = null;
		else
			for (Node<T> node : children)
				list.add((Node<T>) node.clone());
	
		return new Node<T>(ID, label, type, parent, list,
				(T) property.getClone(), this.data);
	}
	
	
	@SuppressWarnings("unchecked")
	public Node<T> blindClone() throws CloneNotSupportedException {
		return new Node<T>(ID, label, type, (T) property.getClone(), data);
	}
	
	
	/**
	 * Search using DFS all labels in a node (tree)
	 * 
	 * @param label
	 * @return a list of nodes with the same label or null if no label found
	 */
	public ArrayList<Node<T>> getNodesByLabel(String label) {
		return getNodesByLabel(label, this, new ArrayList<Node<T>>());
	}
	
	private ArrayList<Node<T>> getNodesByLabel(String label, Node<T> node,
			ArrayList<Node<T>> arrayList) {
		if (node.label.equals(label))
			arrayList.add(node);
	
		if (node.children != null)
			for (Node<T> n : node.children) {
				List<Node<T>> list = getNodesByLabel(label, n,
						new ArrayList<Node<T>>());
				if (list != null)
					for (Node<T> nn : list)
						arrayList.add(nn);
			}
		
		return arrayList.size() > 0 ? arrayList : null;
	}
	
	
	/**
	 * Depth first search.
	 * 
	 * @param ID
	 * @return the node with ID = ID or null if ID not found
	 */
	public Node<T> getNodeById(int ID) {
		return getNodeById(ID, this);
	}
	
	private Node<T> getNodeById(int ID, Node<T> node) {
		if (node.ID == ID)
			return node;
		if (node.children != null)
			for (Node<T> ch : node.children) {
				Node<T> n = getNodeById(ID, ch);
				if (n != null)
					return n;
			}
		return null;
	}
	
	@Override
	public String toString() {
		return toXML(this, 0, PROP_ID);
	}
	
	private String toXML(Node<T> node, int indent, int detailCode) {
		String detail = getDetail(node, detailCode);
		if (node.children == null) {
			if (node.data.equals("")) {
				return getIndent(indent) + "<" + node.label + " " + detail
						+ ">" + "</" + node.label + ">";
			} else {
				return getIndent(indent) + "<" + node.label + " " + detail
						+ ">" + node.data + "</" + node.label + ">";
			}
		} else {
			String out = getIndent(indent) + "<" + node.label + " " + detail
					+ ">\n";
			for (Node<T> child : node.children)
				out += toXML(child, indent + 1, detailCode) + "\n";
			if (!node.data.equals(""))
				out += getIndent(indent + 1) + node.data + "\n";
			out += getIndent(indent) + "</" + node.label + ">";
			return out;
		}
	}
	
	
	private String getIndent(int length) {
		String indent = "";
		for (int i = 0; i < length; i++)
			indent += "\t";
		return indent;
	}
	
	
	private static final int TYPE = 1;
	private static final int ID_ = 2;
	private static final int PROP = 3;
	private static final int TYPE_ID = 4;
	private static final int TYPE_PROP = 5;
	private static final int PROP_ID = 6;
	private static final int PROP_ID_TYPE = 7;
	
	private String getDetail(Node<T> node, int detail) {
		switch (detail) {
		case TYPE:
			return " type = \"" + node.type + "\" ";
		case ID_:
			return " ID = \"" + node.ID + "\" ";
		case PROP:
			return " property = \"" + node.property.toString() + "\" ";
		case TYPE_ID:
			return " type = \"" + node.type + "\" ID = \"" + node.ID + "\" ";
		case TYPE_PROP:
			return " type = \"" + node.type + "\" multiplicity = \""
					+ node.property.toString() + "\" ";
		case PROP_ID:
			return " ID = \"" + node.ID + "\" multiplicity = \"" + node.property
					+ "\" ";
		case PROP_ID_TYPE:
			return " type = \"" + node.type + "\" multiplicity = \""
					+ node.property.toString() + "\" ID = \"" + node.ID + "\" ";
		default:
			return "";
		}
	}
	
	public void accept(NodeVisitor<T> v) {
		v.visit(this);
	}
	
	public boolean hasAncestor(Node<ConfigurationNode> cnode) {
		Node<T> iterator = this.parent;
		while (iterator != null) {
			if (cnode.ID == iterator.ID
					&& cnode.property.equals(iterator.property))
				return true;
			iterator = iterator.parent;
		}
		return false;
	}
	
	public int getDepth(int id) {
		Node<T> n = getNodeById(id);
		
		if (n == null)
		{
			return -1;
		}
		
		int count = 0;
		Node<T> it = n;
		while (it != null)
		{
			it = it.parent;
			count++;
		}
		return count;
	}
	
}